<?php
/**
 * 农历操作基础类
 * @author chunyang.shu
 * @date 2016-07-06
 */
class np_lunar_calendar
{
	/**
	 * 农历天干描述数组
	 * @var array
	 */
	protected $arr_heavenly_stems = array(
		 '庚', '辛', '壬', '癸', '甲', '乙', '丙', '丁', '戊', '己'
	);
	
	/**
	 * 农历地支描述数组
	 * @var array
	*/
	protected $arr_earthly_branches = array(
		 '申', '酉', '戌', '亥', '子', '丑', '寅', '卯', '辰', '巳', '午', '未'
	);
	
	/**
	 * 农历二十四节气描述数组
	 * @var array
	 */
	protected $arr_solar_terms = array(
		'小寒', '大寒', '立春', '雨水', '惊蛰', '春分', '清明', '谷雨', '立夏', '小满', '芒种', '夏至',
		'小暑', '大暑', '立秋', '处暑', '白露', '秋分', '寒露', '霜降', '立冬', '小雪', '大雪', '冬至'
	);
	
	/**
	 * 农历二十四节气计算信息
	 * @var array
	 */
	protected $arr_solar_terms_info = array(
		0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693,
		263343, 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758
	);
	
	/**
	 * 十二生肖描述数组
	 * @var array
	 */
	protected $arr_chinese_zodiacs = array(
		 '猴', '鸡', '狗', '猪', '鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊'
	);
	
	/**
	 * 农历月描述数组
	 * @var array
	*/
	protected $arr_lunar_months = array(
		'正月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '冬月', '腊月'
	);
	
	/**
	 * 1到10的罗马数字对应的中文描述数组
	 * @var array
	 */
	protected $arr_number_chinese_desc = array(
		'一', '二', '三', '四', '五', '六', '七', '八', '九', '十'
	);
	
	/**
	 * 该类支持的最小公历年份
	 * @var number
	 */
	protected $int_min_year = 1891;
	
	/**
	 * 该类支持的最大公历年份
	 * @var number
	 */
	protected $int_max_year = 2100;
	
	/**
	 * 公历转农历对应信息数组
	 * 每个小数组表示一个公历年，从1891年到2100年
	 * 小数组的第一个元素表示公历年对应的农历年的闰月月份，0表示没有闰月
	 * 小数组的第二个元素表示公历年对应的农历年的正月初一的公历月份
	 * 小数组的第三个元素表示公历年对应的农历年的正月初一的公历日
	 * 小数组的第四个元素用来计算公年对应的农历年每个月的天数
	 * @var array
	 */
	protected $arr_lunar_convert_list = array(
		array(0, 2, 9, 21936),
		array(6, 1, 30, 9656),
		array(0, 2, 17, 9584),
		array(0, 2, 6, 21168),
		array(5, 1, 26, 43344),
		array(0, 2, 13, 59728),
		array(0, 2, 2, 27296),
		array(3, 1, 22, 44368),
		array(0, 2, 10, 43856),
		array(8, 1, 30, 19304),
		array(0, 2, 19, 19168),
		array(0, 2, 8, 42352),
		array(5, 1, 29, 21096),
		array(0, 2, 16, 53856),
		array(0, 2, 4, 55632),
		array(4, 1, 25, 27304),
		array(0, 2, 13, 22176),
		array(0, 2, 2, 39632),
		array(2, 1, 22, 19176),
		array(0, 2, 10, 19168),
		array(6, 1, 30, 42200),
		array(0, 2, 18, 42192),
		array(0, 2, 6, 53840),
		array(5, 1, 26, 54568),
		array(0, 2, 14, 46400),
		array(0, 2, 3, 54944),
		array(2, 1, 23, 38608),
		array(0, 2, 11, 38320),
		array(7, 2, 1, 18872),
		array(0, 2, 20, 18800),
		array(0, 2, 8, 42160),
		array(5, 1, 28, 45656),
		array(0, 2, 16, 27216),
		array(0, 2, 5, 27968),
		array(4, 1, 24, 44456),
		array(0, 2, 13, 11104),
		array(0, 2, 2, 38256),
		array(2, 1, 23, 18808),
		array(0, 2, 10, 18800),
		array(6, 1, 30, 25776),
		array(0, 2, 17, 54432),
		array(0, 2, 6, 59984),
		array(5, 1, 26, 27976),
		array(0, 2, 14, 23248),
		array(0, 2, 4, 11104),
		array(3, 1, 24, 37744),
		array(0, 2, 11, 37600),
		array(7, 1, 31, 51560),
		array(0, 2, 19, 51536),
		array(0, 2, 8, 54432),
		array(6, 1, 27, 55888),
		array(0, 2, 15, 46416),
		array(0, 2, 5, 22176),
		array(4, 1, 25, 43736),
		array(0, 2, 13, 9680),
		array(0, 2, 2, 37584),
		array(2, 1, 22, 51544),
		array(0, 2, 10, 43344),
		array(7, 1, 29, 46248),
		array(0, 2, 17, 27808),
		array(0, 2, 6, 46416),
		array(5, 1, 27, 21928),
		array(0, 2, 14, 19872),
		array(0, 2, 3, 42416),
		array(3, 1, 24, 21176),
		array(0, 2, 12, 21168),
		array(8, 1, 31, 43344),
		array(0, 2, 18, 59728),
		array(0, 2, 8, 27296),
		array(6, 1, 28, 44368),
		array(0, 2, 15, 43856),
		array(0, 2, 5, 19296),
		array(4, 1, 25, 42352),
		array(0, 2, 13, 42352),
		array(0, 2, 2, 21088),
		array(3, 1, 21, 59696),
		array(0, 2, 9, 55632),
		array(7, 1, 30, 23208),
		array(0, 2, 17, 22176),
		array(0, 2, 6, 38608),
		array(5, 1, 27, 19176),
		array(0, 2, 15, 19152),
		array(0, 2, 3, 42192),
		array(4, 1, 23, 53864),
		array(0, 2, 11, 53840),
		array(8, 1, 31, 54568),
		array(0, 2, 18, 46400),
		array(0, 2, 7, 46752),
		array(6, 1, 28, 38608),
		array(0, 2, 16, 38320),
		array(0, 2, 5, 18864),
		array(4, 1, 25, 42168),
		array(0, 2, 13, 42160),
		array(10, 2, 2, 45656),
		array(0, 2, 20, 27216),
		array(0, 2, 9, 27968),
		array(6, 1, 29, 44448),
		array(0, 2, 17, 43872),
		array(0, 2, 6, 38256),
		array(5, 1, 27, 18808),
		array(0, 2, 15, 18800),
		array(0, 2, 4, 25776),
		array(3, 1, 23, 27216),
		array(0, 2, 10, 59984),
		array(8, 1, 31, 27432),
		array(0, 2, 19, 23232),
		array(0, 2, 7, 43872),
		array(5, 1, 28, 37736),
		array(0, 2, 16, 37600),
		array(0, 2, 5, 51552),
		array(4, 1, 24, 54440),
		array(0, 2, 12, 54432),
		array(0, 2, 1, 55888),
		array(2, 1, 22, 23208),
		array(0, 2, 9, 22176),
		array(7, 1, 29, 43736),
		array(0, 2, 18, 9680),
		array(0, 2, 7, 37584),
		array(5, 1, 26, 51544),
		array(0, 2, 14, 43344),
		array(0, 2, 3, 46240),
		array(4, 1, 23, 46416),
		array(0, 2, 10, 44368),
		array(9, 1, 31, 21928),
		array(0, 2, 19, 19360),
		array(0, 2, 8, 42416),
		array(6, 1, 28, 21176),
		array(0, 2, 16, 21168),
		array(0, 2, 5, 43312),
		array(4, 1, 25, 29864),
		array(0, 2, 12, 27296),
		array(0, 2, 1, 44368),
		array(2, 1, 22, 19880),
		array(0, 2, 10, 19296),
		array(6, 1, 29, 42352),
		array(0, 2, 17, 42208),
		array(0, 2, 6, 53856),
		array(5, 1, 26, 59696),
		array(0, 2, 13, 54576),
		array(0, 2, 3, 23200),
		array(3, 1, 23, 27472),
		array(0, 2, 11, 38608),
		array(11, 1, 31, 19176),
		array(0, 2, 19, 19152),
		array(0, 2, 8, 42192),
		array(6, 1, 28, 53848),
		array(0, 2, 15, 53840),
		array(0, 2, 4, 54560),
		array(5, 1, 24, 55968),
		array(0, 2, 12, 46496),
		array(0, 2, 1, 22224),
		array(2, 1, 22, 19160),
		array(0, 2, 10, 18864),
		array(7, 1, 30, 42168),
		array(0, 2, 17, 42160),
		array(0, 2, 6, 43600),
		array(5, 1, 26, 46376),
		array(0, 2, 14, 27936),
		array(0, 2, 2, 44448),
		array(3, 1, 23, 21936),
		array(0, 2, 11, 37744),
		array(8, 2, 1, 18808),
		array(0, 2, 19, 18800),
		array(0, 2, 8, 25776),
		array(6, 1, 28, 27216),
		array(0, 2, 15, 59984),
		array(0, 2, 4, 27424),
		array(4, 1, 24, 43872),
		array(0, 2, 12, 43744),
		array(0, 2, 2, 37600),
		array(3, 1, 21, 51568),
		array(0, 2, 9, 51552),
		array(7, 1, 29, 54440),
		array(0, 2, 17, 54432),
		array(0, 2, 5, 55888),
		array(5, 1, 26, 23208),
		array(0, 2, 14, 22176),
		array(0, 2, 3, 42704),
		array(4, 1, 23, 21224),
		array(0, 2, 11, 21200),
		array(8, 1, 31, 43352),
		array(0, 2, 19, 43344),
		array(0, 2, 7, 46240),
		array(6, 1, 27, 46416),
		array(0, 2, 15, 44368),
		array(0, 2, 5, 21920),
		array(4, 1, 24, 42448),
		array(0, 2, 12, 42416),
		array(0, 2, 2, 21168),
		array(3, 1, 22, 43320),
		array(0, 2, 9, 26928),
		array(7, 1, 29, 29336),
		array(0, 2, 17, 27296),
		array(0, 2, 6, 44368),
		array(5, 1, 26, 19880),
		array(0, 2, 14, 19296),
		array(0, 2, 3, 42352),
		array(4, 1, 24, 21104),
		array(0, 2, 10, 53856),
		array(8, 1, 30, 59696),
		array(0, 2, 18, 54560),
		array(0, 2, 7, 55968),
		array(6, 1, 27, 27472),
		array(0, 2, 15, 22224),
		array(0, 2, 5, 19168),
		array(4, 1, 25, 42216),
		array(0, 2, 12, 42192),
		array(0, 2, 1, 53584),
		array(2, 1, 21, 55592),
		array(0, 2, 9, 54560)
	);
	
	/**
	 * 当前公历转农历的转换信息
	 * @var array
	 */
	protected $arr_convert_info;
	
	/**
	 * 根据日期获取农历信息
	 * @param string $day 公历日期
	 * @return array(
	 * 		'chinese_zodiacs'	=> '农历生肖',
	 * 		'ganzhi_year'		=> '农历年份干支描述',
	 * 		'month'				=> '农历月份描述',
	 * 		'ganzhi_month'		=> '农历月份干支描述',
	 * 		'day'				=> '农历日期描述',
	 * 		'ganzhi_day'		=> '农历日期干支描述',
	 * 		'solar_term'		=> '节气名称'
	 * )
	 * @author chunyang.shu
	 * @date 2016-07-06
	 */
	public function get_lunar_calendar_info($year, $month, $day)
	{
		// 如果年份超出支持范围，返回false
		if ($year < $this->int_min_year || $year > $this->int_max_year)
		{
			return false;
		}
		
		// 获取农历转换信息
		$this->arr_convert_info = $this->arr_lunar_convert_list[$year - $this->int_min_year];
		
		// 计算传入日期到传入年份农历正月初一对应的公历日期的天数
		$int_between_days = $this->_get_days_between_date($year, $month, $day, $year, $this->arr_convert_info[1], $this->arr_convert_info[2]);
		
		// 获取该天的二十四节气信息
		$str_solar_term = $this->_get_solar_term_info_by_date($year, $month, $day);
		
		// 根据差异天数计算农历信息
		$int_lunar_month = $int_lunar_day = 0;
		if ($int_between_days == 0)
		{
			$int_lunar_month = $int_lunar_day = 1;
		}
		else
		{
			if ($int_between_days < 0)
			{
				$year--;
				$this->arr_convert_info = $this->arr_lunar_convert_list[$year - $this->int_min_year];
			}
			
			// 获取公历年对应的农历年每个月的天数
			$arr_lunar_month_days = $this->_get_lunar_month_days();
			
			// 如果传入日期在当年对应的正月初一前，则根据前一公历年对应的农历计算
			if ($int_between_days < 0)
			{
				$int_between_days += array_sum($arr_lunar_month_days);
			}
			
			// 判断当前日期属于农历年的第几个月
			$int_key = $int_count = 0;
			foreach ($arr_lunar_month_days as $int_month_days)
			{
				$int_key++;
				$int_count += $int_month_days;
				if ($int_between_days == $int_count)
				{
					// 当前日期与正月初一的间隔天数等于某个月及其前面所有月的总天数，当前日期为下个月的第一天
					$int_lunar_month = $int_key + 1;
					$int_lunar_day = 1;
					break;
				}
				else if ($int_between_days < $int_count)
				{
					// 当前日期处于某个月之内，计算当前日期为该月的第几天
					$int_lunar_month = $int_key;
					if ($int_key == 1)
					{
						// 当前日期为该年的第一个月
						$int_lunar_day = $int_between_days + 1;
					}
					else
					{
						$int_lunar_day = $int_between_days - ($int_count - $int_month_days) + 1;
					}
					break;
				}
			}
		}
		
		// 返回结果
		$result = array(
			'chinese_zodiacs'	=> $this->_get_chinese_zodiac($year),
			'ganzhi_year'		=> $this->_get_ganzhi_year_name($year),
			'month'				=> $this->_get_lunar_month_name($int_lunar_month),
			'ganzhi_month'		=> '',
			'day'				=> $this->_get_lunar_day_name($int_lunar_day),
			'ganzhi_day'		=> '',
			'solar_term'		=> $str_solar_term
		);
		return $result;
	}
	
	/**
	 * 根据公历年获取农历年的天干地支
	 * @param number $year 公历年
	 * @return string
	 * @author chunyang.shu
	 * @date 2016-07-06
	 */
	protected function _get_ganzhi_year_name($year)
	{
		// 取公历年最后一位数字，找到天干描述数组中的对应，即为天干描述
		$arr_year = str_split(strval($year));
		$str_heavenly_stem = $this->arr_heavenly_stems[array_pop($arr_year)];
		
		// 用公历年数字除以12得到的余数，找到地支描述数组中的对应，即为地支描述
		$str_earthly_branch = $this->arr_earthly_branches[$year % 12];
		
		// 用天干描述连上地支描述，即为天干地址
		return $str_heavenly_stem . $str_earthly_branch;
	}
	
	/**
	 * 根据第几月获取农历月份名称
	 * @param number $month 农历月
	 * @return string
	 * @author chunyang.shu
	 * @date 2016-07-06
	 */
	protected function _get_lunar_month_name($month)
	{
		// 获取年份的闰月信息
		$int_leap_month = $this->arr_convert_info[0];
		
		// 该年不需要闰月
		if ($int_leap_month == 0)
		{
			return $this->arr_lunar_months[$month - 1];
		}
		
		// 要获取的月份是闰月
		if ($month == $int_leap_month + 1)
		{
			return '闰' . $this->arr_lunar_months[$month - 1 - 1];
		}
		// 要获取的月份在闰月之前
		if ($month < $int_leap_month + 1)
		{
			return $this->arr_lunar_months[$month - 1];
		}
		// 要获取的月份在闰月之后
		if ($month > $int_leap_month + 1)
		{
			return $this->arr_lunar_months[$month - 1 - 1];
		}
	}
	
	/**
	 * 根据第几天获取农历天描述
	 * @param number $day 农历日期
	 * @return false 日期非法    string 农历日期描述
	 * @author chunyang.shu
	 * @date 2016-07-06
	 */
	protected function _get_lunar_day_name($day)
	{
		$result = false;
		if ($day <= 10)
		{
			$result = '初' . $this->arr_number_chinese_desc[$day - 1];
		}
		else if ($day > 10 && $day < 20)
		{
			$result = '十' . $this->arr_number_chinese_desc[$day - 10 - 1];
		}
		else if ($day == 20)
		{
			$result = '二十';
		}
		else if ($day > 20 && $day < 30)
		{
			$result = '廿' . $this->arr_number_chinese_desc[$day - 20 - 1];
		}
		else if ($day == 30)
		{
			$result = '三十';
		}
		return $result;
	}
	
	/**
	 * 根据公历年获取中国十二生肖描述
	 * @param number $year 公历年
	 * @author chunyang.shu
	 * @date 2016-07-06
	 */
	protected function _get_chinese_zodiac($year)
	{
		// 用公历年数字除以12得到的余数，找到生肖描述数组中的对应，即为该年的生肖
		$str_chinese_zodiac = $this->arr_chinese_zodiacs[$year % 12];
		return $str_chinese_zodiac;
	}
	
	/**
	 * 根据公历年获取对应的农历年每个月的天数
	 * @return array
	 * @author chunyang.shu
	 * @date 2016-07-06
	 */
	protected function _get_lunar_month_days()
	{
		$int_leap_month = $this->arr_convert_info[0];
		$str_bit = decbin($this->arr_convert_info[3]);
		$arr_bit = str_split(str_pad($str_bit, 16, '0', STR_PAD_LEFT));
		$arr_bit = array_slice($arr_bit, 0, ($int_leap_month == 0) ? 12 : 13);
		$result = array();
		foreach ($arr_bit as $int_bit)
		{
			$result[] = $int_bit + 29;
		}
		return $result;
	}
	
	/**
	 * 获取两个日期建的天数
	 * @param number $year1
	 * @param number $month1
	 * @param number $day1
	 * @param number $year2
	 * @param number $month2
	 * @param number $day2
	 * @return number
	 * @author chunyang.shu
	 * @date 2016-07-06
	 */
	protected function _get_days_between_date($year1, $month1, $day1, $year2, $month2, $day2)
	{
		$time1 = mktime(0, 0, 0, $month1, $day1, $year1);
		$time2 = mktime(0, 0, 0, $month2, $day2, $year2);
		$result = intval(($time1 - $time2) / 24 / 3600);
		return $result;
	}
	
	/**
	 * 根据公历年月日获取农历二十四节气信息
	 * @param string $year 公历年
	 * @param string $month 农历月
	 * @param string $day 公历日
	 * @return string 空字符串表示该日期不是节气
	 * @author chunyang.shu
	 * @date 2016-07-08
	 */
	protected function _get_solar_term_info_by_date($year, $month, $day)
	{
		// 根据月获取该月两个节气对应的公历日期时间戳
		$int_solar_term_num1 = ($month - 1) * 2;
		$int_solar_term_num2 = ($month - 1) * 2 + 1;
		$int_solar_term_time1 = $this->_get_date_by_year_and_solar_term_num($year, $int_solar_term_num1);
		$int_solar_term_time2 = $this->_get_date_by_year_and_solar_term_num($year, $int_solar_term_num2);
		if (date('Y-m-d', $int_solar_term_time1) == date('Y-m-d', mktime(0, 0, 0, $month, $day, $year)))
		{
			return $this->arr_solar_terms[$int_solar_term_num1];
		}
		if (date('Y-m-d', $int_solar_term_time2) == date('Y-m-d', mktime(0, 0, 0, $month, $day, $year)))
		{
			return $this->arr_solar_terms[$int_solar_term_num2];
		}
		return '';
	}
	
	/**
	 * 根据公历年和节气编号获取节气对应的公历时间戳
	 * @param string $year 公历年
	 * @param number $solar_term_num 节气编号
	 * @return number 时间戳
	 * @author chunyang.shu
	 * @date 2016-07-08
	 */
	protected function _get_date_by_year_and_solar_term_num($year, $solar_term_num)
	{
		// 节气对应的GMT时间戳
		$int_time = round((31556925974.7 * ($year - 1900) + $this->arr_solar_terms_info[$solar_term_num] * 60000 - 2208549300000) / 1000);
		
		// 转换成格林威治标准时间戳
		$int_time -= date('Z');
		return $int_time;
	}
}